/**
 * 
 */
package qy.jalgotrade.technical;

import java.time.ZonedDateTime;

import org.apache.commons.math3.stat.StatUtils;

import qy.jalgotrade.bar.Bar;
import qy.jalgotrade.dataseries.BarDataSeries;

/**
 * <pre>
 * Average True Range filter as described in http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_true_range_atr
 * pyalgotrade.technical.atr.ATR: TR = max(high - low, abs(high - pre-close), abs(pre-close - low)), ATR = SMA(TR, N)
 * </pre>
 * 
 * @author c-geo
 *
 */
public class ATR extends EventBasedFilter<Double> {

	/**
	 * <pre>
	 * This event window will calculate and hold true-range values.
	 * Formula from http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:average_true_range_atr.
	 * </pre>
	 * 
	 * @author c-geo
	 *
	 */
	protected class ATREventWindow extends EventWindow<Double> {

		private boolean __useAdjustedValues;

		private double __prevClose;

		private double __value;

		/**
		 * 
		 * @param period
		 * @param useAdjustedValues
		 */
		public ATREventWindow(int period, boolean useAdjustedValues) {

			super(period, Double.class);
			assert period > 1;
			__useAdjustedValues = useAdjustedValues;
			__prevClose = Double.NaN;
			__value = Double.NaN;
		}

		/**
		 * 
		 * @param value
		 * @return
		 */
		protected double _calculateTrueRange(Bar value) {

			double ret = Double.NaN;
			if (Double.isNaN(__prevClose)) {
				ret = value.getHigh(__useAdjustedValues) - value.getLow(__useAdjustedValues);
			} else {
				double tr1 = value.getHigh(__useAdjustedValues) - value.getLow(__useAdjustedValues);
				double tr2 = Math.abs(value.getHigh(__useAdjustedValues) - __prevClose);
				double tr3 = Math.abs(value.getLow(__useAdjustedValues) - __prevClose);
				ret = Math.max(Math.max(tr1, tr2), tr3);
			}
			return ret;
		}

		/*
		 * (non-Javadoc)
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#onNewValue(java.time.ZonedDateTime, java.lang.Object)
		 */
		@Override
		public void onNewValue(ZonedDateTime dateTime, Object value) {

			double tr = _calculateTrueRange((Bar) value);
			super.onNewValue(dateTime, tr);
			__prevClose = ((Bar) value).getClose(__useAdjustedValues);

			if (value != null && windowFull()) {
				if (Double.isNaN(__value)) {

//					__value = StatUtils.mean(((DoubleList) getValues()).toDoubleArray());
					__value = StatUtils.mean((double[]) toPrimitiveArray());
				} else {
					__value = (__value * (getWindowSize() - 1) + tr) / getWindowSize();
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * @see qy.jalgotrade.technical.EventBasedFilter.EventWindow#getValue()
		 */
		@Override
		public Double getValue() {

			return __value;
		}
	}

	/**
	 * 
	 * @param barDataSeries The BarDataSeries instance being filtered.
	 * @param period        The average period. Must be > 1.
	 * @throws Exception
	 */
	public ATR(BarDataSeries barDataSeries, int period) throws Exception {

		this(barDataSeries, period, false, 0);
	}

	/**
	 * 
	 * @param barDataSeries     The BarDataSeries instance being filtered.
	 * @param period            The average period. Must be > 1.
	 * @param useAdjustedValues True to use adjusted Low/High/Close values.
	 * @throws Exception
	 */
	public ATR(BarDataSeries barDataSeries, int period, boolean useAdjustedValues) throws Exception {

		this(barDataSeries, period, useAdjustedValues, 0);
	}

	/**
	 * 
	 * @param barDataSeries     The BarDataSeries instance being filtered.
	 * @param period            The average period. Must be > 1.
	 * @param useAdjustedValues True to use adjusted Low/High/Close values.
	 * @param maxLen            The maximum number of values to hold. Once a bounded length is full,
	 *                          when new items are added, a corresponding number of items are discarded
	 *                          from the opposite end. If None then dataseries.DEFAULT_MAX_LEN is used.
	 * @throws Exception
	 */
	public ATR(BarDataSeries barDataSeries, int period, boolean useAdjustedValues, int maxLen) throws Exception {

		super(maxLen);
		ATREventWindow win = new ATREventWindow(period, useAdjustedValues);
		_init(barDataSeries, win);
	}
}
